// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System;

namespace SnapObjects.Data
{
    internal class UpdateSingularExecutor : UpdateExecutor
    {
        public static UpdateSingularExecutor Instance => new UpdateSingularExecutor();

        public virtual UpdateResult Update(
            DataContext dataContext,
            IReadOnlyList<IUpdatable> updatables)
        {
            var result = new UpdateResult();
            var saveContext = new SaveContext();

            var modelUpdateCommand = new ModelUpdateCommand(dataContext);
            var sqlUpdateCommand = new SqlUpdateCommand(dataContext);

            foreach (var updatable in updatables)
            {
                if (updatable.UpdateType == UpdateType.ModelUpdate)
                {
                    var lastResult = new UpdateResult();
                    var updatableModel = (IUpdatableModel)updatable;
                    
                    if (updatableModel.IgnoreSave)
                    {
                        updatableModel.PerformSetValueOperations();
                        continue;
                    }
                    
                    //Check if set Cancelled flag in action
                    if (this.ExecuteSaveAction(updatableModel, SaveActionBehavior.BeforeSave, saveContext))
                    {
                        result.Cancelled = true;
                        break;
                    }

                    try
                    {
                        modelUpdateCommand.Execute(updatableModel);
                    }
                    catch (Exception exception)
                    {
                        throw new ModelUpdateException(
                            exception.Message, exception, ((UpdatableModel)updatable).Current);
                    }
                    
                    this.UpdateResult(ref lastResult, modelUpdateCommand);
                    saveContext.LastDbResult = lastResult;

                    updatableModel.PerformSetValueOperations();

                    //Check if set Cancelled flag in action
                    if (this.ExecuteSaveAction(updatableModel, SaveActionBehavior.AfterSave, saveContext))
                    {
                        result.Cancelled = true;
                        break;
                    }

                    this.UpdateResult(ref result, modelUpdateCommand);
                }
                else if (updatable.UpdateType == UpdateType.SqlUpdate ||
                    updatable.UpdateType == UpdateType.SqlBuilder)
                {
                    sqlUpdateCommand.Execute((IUpdatableSql)updatable);

                    this.UpdateResult(ref result, sqlUpdateCommand);
                }
                else if (updatable.UpdateType == UpdateType.Action)
                {
                    //Check if set Cancelled flag in action
                    if (this.ExecuteSaveAction((IUpdatableAction)updatable, saveContext))
                    {
                        result.Cancelled = true;
                        break;
                    }
                }
                else if (updatable.UpdateType == UpdateType.OtherUpdatable)
                {
                    this.ExecuteOtherUpdatable(dataContext, (UpdatableObject)updatable, ref result);
                }
            }

            return result;
        }

        public virtual async Task<UpdateResult> UpdateAsync(
            DataContext dataContext,
            IReadOnlyList<IUpdatable> updatables,
            CancellationToken cancellationToken)
        {
            var result = new UpdateResult();
            var saveContext = new SaveContext();

            var modelUpdateCommand = new ModelUpdateCommand(dataContext);
            var sqlUpdateCommand = new SqlUpdateCommand(dataContext);

            foreach (var updatable in updatables)
            {
                if (updatable.UpdateType == UpdateType.ModelUpdate)
                {
                    var lastResult = new UpdateResult();
                    var updatableModel = (IUpdatableModel)updatable;

                    if (updatableModel.IgnoreSave)
                    {
                        updatableModel.PerformSetValueOperations();
                        continue;
                    }

                    //Check if set Cancelled flag in action
                    if (this.ExecuteSaveAction(updatableModel, SaveActionBehavior.BeforeSave, saveContext))
                    {
                        result.Cancelled = true;
                        break;
                    }

                    try
                    {
                        await modelUpdateCommand.ExecuteAsync(updatableModel, cancellationToken);
                    }
                    catch (Exception exception)
                    {
                        throw new ModelUpdateException(
                            exception.Message, exception, ((UpdatableModel)updatable).Current);
                    }

                    this.UpdateResult(ref lastResult, modelUpdateCommand);
                    saveContext.LastDbResult = lastResult;

                    updatableModel.PerformSetValueOperations();

                    //Check if set Cancelled flag in action
                    if (this.ExecuteSaveAction(updatableModel, SaveActionBehavior.AfterSave, saveContext))
                    {
                        result.Cancelled = true;
                        break;
                    }

                    this.UpdateResult(ref result, modelUpdateCommand);
                }
                else if (updatable.UpdateType == UpdateType.SqlUpdate ||
                    updatable.UpdateType == UpdateType.SqlBuilder)
                {
                    await sqlUpdateCommand.ExecuteAsync((IUpdatableSql)updatable, cancellationToken);

                    //result.SqlCUDCount += sqlUpdateCommand.SqlCUDCount;
                    this.UpdateResult(ref result, sqlUpdateCommand);
                }
                else if (updatable.UpdateType == UpdateType.Action)
                {
                    //Check if set Cancelled flag in action
                    if (this.ExecuteSaveAction((IUpdatableAction)updatable, saveContext))
                    {
                        result.Cancelled = true;
                        break;
                    }
                }
                else if (updatable.UpdateType == UpdateType.OtherUpdatable)
                {
                    result = await this.ExecuteOtherUpdatableAsync(
                        dataContext, (UpdatableObject)updatable, result, cancellationToken);
                }
            }

            return result;
        }
    }
}
